BizTalk support for client callbacks
BizTalk has mixed support for WCF-based callbacks. On the receiving side, BizTalk does not have an explicit adapter for the WsDualHttpBinding,
but does have "hidden" support for this WCF feature. When sending
messages to services, BizTalk does not support duplex communication.
However, there are mechanisms for mirroring this behavior as well.
First, let's look at the
scenario where BizTalk is the service consumer. This is where BizTalk
Server's capabilities shine and enable the most flexible and
interoperable ways for services to send data back to calling clients.
What are we really trying to accomplish? In essence, we want a service
to tell the client that something happened well after the initial
connection was concluded. In a straight WCF scenario, the core challenge
is devising a way to transmit data in a non-client-blocking and
WCF-compliant way. In the loosely-coupled, server-side BizTalk
environment, we have two ways to retrieve data from services.
First off, BizTalk can call a
service, get back a token, and then poll for changes stored within an
agreed upon repository. The token is needed so that we can poll for our
unique result. Think of getting back a Federal Express tracking number
when shipping a product and using that tracking number to poll their
website for status updates. Within an orchestration, a loop can be set
up which polls the repository (such as database, SOAP endpoint, RESTful
HTTP resource) at an agreed upon interval, and only proceeds once the
expected result is returned by the polling instance. This model is
perfectly acceptable, but it does force the orchestration to wastefully
poll when the repository has yet to be updated by the service.
The better way to
receive callbacks into BizTalk is to rely on BizTalk adapters, which
natively accept a "push" from the service. This could be a BizTalk WCF
service endpoint which is executed by the target service, or something
more rudimentary such as BizTalk listening for a file or receiving an
email. In those cases, an orchestration is bound to this callback
receive location and only proceeds once the data is absorbed by the
BizTalk adapter. This provides the BizTalk with a wide range of options
for receiving responses to asynchronous service invocation.
What about BizTalk Server
2009 acting as the service provider for others? In this case, we can
also exploit all the native BizTalk adapters when sending callback
information to client applications. For instance, a service client can
instantiate a BizTalk orchestration and expect a response once the
process is complete. Because the client does not know when BizTalk will
finish the workflow, they want to exploit a transport mechanism that
will reach them whether they are online or offline. In that case, the
client puts an email address in the request message header, and BizTalk
utilizes dynamic send ports to send the expected email acknowledgement
once the long-running process is complete. You essentially have the
entire BizTalk adapter stack at your disposal when choosing how to send
notifications back to service clients.
How about actually using the
WCF duplex bindings to receive messages into BizTalk and send a later
response? On the surface, this doesn't seem particularly easy. There is
no BizTalk adapter for the WsDualHttpBinding
and no way to define a callback contract in a receive location or
orchestration. Nonetheless, it is indeed possible to apply the WsDualHttpBinding and get duplex behavior from BizTalk Server.
First of all, we need
an orchestration that contains a request/response logical port. In my
case, I designed an orchestration that takes in our adverse event
message, waits for three minutes, and concludes by sending an
acknowledgement message. Why the three minute wait? I want to make sure
that we are not simply doing a synchronous behavior that looks
asynchronous so I'm using a time interval outside the boundaries of the
standard service timeout.
Once this orchestration
is built and deployed, we next configure a physical request/response
receive port and location. The receive location should use the WCF-Custom adapter, which in turn applies the wsDualHttpBinding. By using this configuration, we are choosing to host our HTTP endpoint within the in-process BizTalk host service.
At this point, the
BizTalk-based configuration is relatively complete. As I mentioned
earlier, we don't have the ability to define the callback contract
relationship within BizTalk. Also, the WSDL produced by the WCF-Custom
receive location does not match the structure produced from a standard
WCF service containing callback instructions. So what do we do? The
easiest thing to do is define your own WCF contract that represents the
BizTalk endpoint and messages.
First, we need data contract representations of the XSD messages circulating within BizTalk Server. Fortunately for us, WCF's svcutil.exe
tool can take XSD schemas and generate corresponding WCF data
contracts. For instance, the following command produces a class file
corresponding to our adverse event schema:
svcutil BizTalkAdverseEvent_XML.xsd /dconly
Pitfall
The /dconly command in the svcutil.exe tool only works on schemas where the ElementFormDefault value is set to Qualified. Hence, this is yet another good reason to namespace qualify all of your BizTalk schemas.
Once we have class files
for both our request and callback schemas, we next need the actual
interface definition for the interaction. I defined one interface for
the outbound request, and then a callback interface that handles the
response from BizTalk.
[ServiceContract(
Namespace= "http://Seroter.BizTalkSOA.Chapter6.BizTalkBits.BizTalkAdverseEvent_XML",
CallbackContract=typeof(IBizTalkAdverseEventDuplexCallback),
SessionMode = SessionMode.Required)]
public interface IBizTalkAdverseEventDuplex
{
[OperationContract(IsOneWay = true, Action = "PublishAdverseEvent")]
void PublishAdverseEvent(BizTalkAdverseEvent BizTalkAdverseEvent);
}
public interface IBizTalkAdverseEventDuplexCallback
{
[XmlSerializerFormat]
[OperationContract(IsOneWay = true, Action = "PublishAdverseEventResponse")]
void AEResult(BizTalkAdverseEventAction BizTalkAdverseEventAction);
}